{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Разбор списка ФСЭМ\n",
    "\n",
    "Миньюс РФ ведёт [список экстремистских материалов](http://minjust.ru/ru/extremist-materials), которые должны быть заблокированы на территории России. \n",
    "![](thumb.png)\n",
    "\n",
    "Список это несколько тысяч записей вида:\n",
    "```\n",
    "Музыкальный альбом \"Музыка белых\", автор - Музыкальная группа Order\n",
    "\n",
    "Аудиофайл – «Русский стяг – Я не хочу быть толерантным (Short).mp3», продолжительностью 3 мин. 25 сек. и размером 5,49 мб. (5 764 283 байта)\n",
    "\n",
    "Видеофайл «Бабушка отматерила чурку» продолжительностью 1 мин., начинающийся со слов «Я тебя ведь зарежу....» и заканчивающийся словами «... убью» \n",
    "...\n",
    "```\n",
    "\n",
    "Чтобы блокировать их в Интернете, нужно извлекать из записей ключевую информацию, например:\n",
    "```\n",
    "[[Видеоматериал]] [[«Слава РУСИ»]], размещенный на интернет-сайте, имеющий электронный адрес [[http://vkontakte.ru]] (решение [[Промышленного районного суда]] г. Курска от [[04.03.2013]]);\n",
    "{\n",
    "  \"items\": [\n",
    "    {\n",
    "      \"type\": \"видеоматериал\",\n",
    "      \"titles\": [\n",
    "        \"«Слава РУСИ»\"\n",
    "      ],\n",
    "      \"urls\": [\n",
    "        \"http://vkontakte.ru\"\n",
    "      ]\n",
    "    }\n",
    "  ],\n",
    "  \"decision\": {\n",
    "    \"sud\": {\n",
    "      \"name\": \"промышленный\",\n",
    "      \"type\": \"районный\"\n",
    "    },\n",
    "    \"date\": {\n",
    "      \"year\": 2013,\n",
    "      \"month\": 3,\n",
    "      \"day\": 4\n",
    "    }\n",
    "  }\n",
    "}\n",
    "\n",
    "```\n",
    "\n",
    "В этом примере показано, как с помощью Yargy-парсера приводить записи ФСЭМ к структурированному виду. Качество получается не идеальное, но и цель этого примера не получить продакш решение. При желании можно доработать правила, улучшить результат."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from random import seed, sample\n",
    "\n",
    "\n",
    "def load_lines(path):\n",
    "    with open(path) as file:\n",
    "        for line in file:\n",
    "            yield line.rstrip('\\n')\n",
    "\n",
    "            \n",
    "def make_translation_table(source, target):\n",
    "    assert len(source) == len(target)\n",
    "    return {\n",
    "        ord(a): ord(b)\n",
    "        for a, b in zip(source, target)\n",
    "    }\n",
    "\n",
    "\n",
    "DASHES_TRANSLATION = make_translation_table(\n",
    "    '‑–—−',\n",
    "    '----'\n",
    ")\n",
    "\n",
    "\n",
    "def normalize_text(text):\n",
    "    return text.translate(DASHES_TRANSLATION)\n",
    "\n",
    "\n",
    "texts = [\n",
    "    normalize_text(_)\n",
    "    for _ in load_lines('texts.txt')\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Grammar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import re\n",
    "import json\n",
    "\n",
    "from ipymarkup import show_span_ascii_markup as show_markup\n",
    "\n",
    "from yargy import (\n",
    "    Parser,\n",
    "    rule, or_,\n",
    "    not_, and_\n",
    ")\n",
    "from yargy.predicates import (\n",
    "    eq, type, caseless, in_, in_caseless,\n",
    "    gte, lte, length_eq,\n",
    "    is_capitalized, normalized,\n",
    "    dictionary, gram,\n",
    ")\n",
    "from yargy.pipelines import (\n",
    "    caseless_pipeline,\n",
    "    morph_pipeline\n",
    ")\n",
    "from yargy.interpretation import (\n",
    "    fact,\n",
    "    attribute\n",
    ")\n",
    "from yargy import interpretation as interp\n",
    "from yargy.relations import gnc_relation\n",
    "from yargy.tokenizer import (\n",
    "    QUOTES,\n",
    "    LEFT_QUOTES,\n",
    "    RIGHT_QUOTES,\n",
    "\n",
    "    MorphTokenizer,\n",
    "    TokenRule\n",
    ")\n",
    "\n",
    "\n",
    "def show_json(data):\n",
    "    print(json.dumps(data, indent=2, ensure_ascii=False))\n",
    "\n",
    "\n",
    "def test_interpretation(rule, *tests):\n",
    "    parser = Parser(rule)\n",
    "    for line, etalon in tests:\n",
    "        match = parser.match(line)\n",
    "        assert match, line\n",
    "        guess = match.fact\n",
    "        assert etalon == guess, guess\n",
    "        \n",
    "\n",
    "def test_match(rule, *tests):\n",
    "    parser = Parser(rule)\n",
    "    for line in tests:\n",
    "        match = parser.match(line)\n",
    "        assert match, line"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Common"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "INT = type('INT')\n",
    "LATIN = type('LATIN')\n",
    "DOT = eq('.')\n",
    "DASH = eq('-')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Type"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "MAIN_TYPE = morph_pipeline([\n",
    "    'dvd-диск',\n",
    "    'альманах',\n",
    "    'аудиовизуальный материал',\n",
    "    'аудиозапись',\n",
    "    'аудиокомпозиция',\n",
    "    'аудиоматериал',\n",
    "    'аудиофайл',\n",
    "    'брошюра',\n",
    "    'вестник',\n",
    "    'видео-файл',\n",
    "    'видеозапись',\n",
    "    'видеоклип',\n",
    "    'видеоматериал',\n",
    "    'видеообращение',\n",
    "    'видеоролик',\n",
    "    'видеофайл',\n",
    "    'видео файл',\n",
    "    'видеофильм',\n",
    "    'видеофонограмма',\n",
    "    'визуальный материал',\n",
    "    'выпуск газеты',\n",
    "    'выпуск листовки',\n",
    "    'высказывания',\n",
    "    'газета',\n",
    "    'графическая работа',\n",
    "    'графическое изображение',\n",
    "    'графический файл',\n",
    "    'демотиватор',\n",
    "    'документ',\n",
    "    'еженедельник',\n",
    "    'журнал',\n",
    "    'журнал-газета',\n",
    "    'издание',\n",
    "    'изображение',\n",
    "    'информация',\n",
    "    'информационное видео',\n",
    "    'информационный материал',\n",
    "    'кинофильм',\n",
    "    'книга',\n",
    "    'компьютерная игра',\n",
    "    'комментарий',\n",
    "    'листовка',\n",
    "    'лозунг',\n",
    "    'материал',\n",
    "    'музыкальный альбом',\n",
    "    'музыкальная композиция',\n",
    "    'музыкальное произведение',\n",
    "    'обозрение',\n",
    "    'печатное издание',\n",
    "    'печатный материал',\n",
    "    'печатная продукция',\n",
    "    'повесть',\n",
    "    'программное обеспечение',\n",
    "    'произведение',\n",
    "    'прокламация',\n",
    "    'публикация',\n",
    "    'рисунок',\n",
    "    'статья',\n",
    "    'стихотворение',\n",
    "    'текст аудиозаписи',\n",
    "    'текст песни',\n",
    "    'текстово-графическое изображение',\n",
    "    'текстовый документ',\n",
    "    'текстовая информация',\n",
    "    'текстовая информация-статус',\n",
    "    'текстовое обращение',\n",
    "    'файл',\n",
    "    'фильм',\n",
    "    'фотография',\n",
    "    'фотоизображение',\n",
    "    'электронный дневник',\n",
    "    'эссе',\n",
    "]).interpretation(\n",
    "    interp.normalized()\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Title"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "QUOTE = in_(QUOTES)\n",
    "LEFT_QUOTE = in_(LEFT_QUOTES)\n",
    "RIGHT_QUOTE = in_(RIGHT_QUOTES)\n",
    "\n",
    "TITLE = or_(\n",
    "    rule(\n",
    "        LEFT_QUOTE,\n",
    "        not_(RIGHT_QUOTE).repeatable(),\n",
    "        RIGHT_QUOTE,\n",
    "    ),\n",
    "    rule(\n",
    "        and_(\n",
    "            QUOTE,\n",
    "            not_(RIGHT_QUOTE)\n",
    "        ),\n",
    "        not_(QUOTE).repeatable(),\n",
    "        and_(\n",
    "            QUOTE,\n",
    "            not_(LEFT_QUOTE)\n",
    "        )\n",
    "    )\n",
    ")\n",
    "\n",
    "test_match(\n",
    "    TITLE,\n",
    "    '«Сознание «Аль-Ваъй»',\n",
    "    '\"Это должен знать Русский»',\n",
    "    '\"Правоохранительные органы РФ фальсифицируют факты и лживо обвиняют Хизб-ут-Тахрир аль-Ислами\"',\n",
    "    # «Вся «правда» о жидо бандеровцах!!!!!!»\n",
    "    # » ООО фирма «\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Url"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "URL_PATTERN = r'(?:http|https|ftp|www|vk|vkontakte|id)[a-z0-9:/\\.?=\\-%&_#\\[\\]]+'\n",
    "URL_RULE = TokenRule('URL', URL_PATTERN)\n",
    "\n",
    "URL = rule(type('URL'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "Name = fact(\n",
    "    'Name',\n",
    "    ['first', 'middle', 'last']\n",
    ")\n",
    "\n",
    "\n",
    "gnc = gnc_relation()\n",
    "\n",
    "LAST = and_(\n",
    "    type('RU'),\n",
    "    is_capitalized()\n",
    ").interpretation(\n",
    "    Name.last.inflected()\n",
    ").match(gnc)\n",
    "\n",
    "FIRST = gram('Name').interpretation(\n",
    "    Name.first.inflected()\n",
    ").match(gnc)\n",
    "\n",
    "MIDDLE = gram('Patr').interpretation(\n",
    "    Name.middle.inflected()\n",
    ").match(gnc)\n",
    "\n",
    "ABBR = and_(\n",
    "    length_eq(1),\n",
    "    is_capitalized()\n",
    ")\n",
    "\n",
    "FIRST_ABBR = ABBR.interpretation(\n",
    "    Name.first.custom(str.lower)\n",
    ")\n",
    "\n",
    "MIDDLE_ABBR = ABBR.interpretation(\n",
    "    Name.middle.custom(str.lower)\n",
    ")\n",
    "\n",
    "\n",
    "NAME = or_(\n",
    "    rule(\n",
    "        LAST,\n",
    "        FIRST_ABBR, DOT,\n",
    "        MIDDLE_ABBR, DOT\n",
    "    ),\n",
    "    rule(\n",
    "        FIRST_ABBR, DOT,\n",
    "        MIDDLE_ABBR, DOT,\n",
    "        LAST\n",
    "    ),\n",
    "    rule(\n",
    "        LAST,\n",
    "        FIRST_ABBR, DOT\n",
    "    ),\n",
    "    rule(\n",
    "        FIRST_ABBR, DOT,\n",
    "        LAST\n",
    "    ),\n",
    "    rule(\n",
    "        FIRST,\n",
    "        MIDDLE,\n",
    "        LAST\n",
    "    ),\n",
    "    rule(\n",
    "        LAST,\n",
    "        FIRST,\n",
    "        MIDDLE\n",
    "    ),\n",
    "    rule(\n",
    "        FIRST,\n",
    "        MIDDLE\n",
    "    ),\n",
    ").interpretation(\n",
    "    Name\n",
    ")\n",
    "\n",
    "\n",
    "test_interpretation(\n",
    "    NAME,\n",
    "    ['Вострягов В.А.', Name(first='в', middle='а', last='востряг')],\n",
    "    ['Т.Н. Галимова', Name(first='т', middle='н', last='галимова')],\n",
    "    ['Е. Сорокоумовой', Name(first='е', last='сорокоумова')],\n",
    "    ['Сергея Владимировича Наумова', Name(first='сергей', middle='владимирович', last='наумов')],\n",
    "    ['Петров Константин Павлович', Name(first='константин', middle='павлович', last='петров')],\n",
    "    ['Волкова Владимира Игоревича', Name(first='владимир', middle='игоревич', last='волков')],\n",
    "    ['Константин Павлович', Name(first='константин', middle='павлович')],\n",
    "    ['Ирину Васильевну', Name(first='ирина', middle='васильевна')]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Author"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "Author = fact(\n",
    "    'Author',\n",
    "    ['type', 'name']\n",
    ")\n",
    "\n",
    "\n",
    "TYPE = morph_pipeline([\n",
    "    'автор',\n",
    "    'пользователь',\n",
    "    'музыкальная группа',\n",
    "    'группа',\n",
    "    'исполнитель',\n",
    "    'движение',\n",
    "    'издательство',\n",
    "]).interpretation(\n",
    "    Author.type.normalized()\n",
    ")\n",
    "\n",
    "AUTHOR = or_(\n",
    "    NAME,\n",
    "    TITLE\n",
    ").interpretation(\n",
    "    Author.name\n",
    ")\n",
    "\n",
    "AUTHOR = rule(\n",
    "    TYPE.optional(),\n",
    "    AUTHOR\n",
    ").interpretation(\n",
    "    Author\n",
    ")\n",
    "\n",
    "\n",
    "test_interpretation(\n",
    "    AUTHOR,\n",
    "    ['музыкальной группы «Циклон Б»', Author(type='музыкальная группа', name='«Циклон Б»')],\n",
    "    [\n",
    "        'автор Петров Константин Павлович',\n",
    "        Author(type='автор', name=Name(first='константин', middle='павлович', last='петров'))\n",
    "    ],\n",
    "    ['Волкова Владимира Игоревича', Author(name=Name(first='владимир', middle='игоревич', last='волков'))]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Number"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "Number = fact(\n",
    "    'Number',\n",
    "    ['value']\n",
    ")\n",
    "\n",
    "\n",
    "PREFIX = or_(\n",
    "    rule('№'),\n",
    "    rule('№', '№'),\n",
    "    rule(normalized('номер'))\n",
    ")\n",
    "\n",
    "VALUE = or_(\n",
    "    rule(INT),\n",
    "    rule(\n",
    "        INT,\n",
    "        DASH,\n",
    "        INT\n",
    "    ),\n",
    "    rule(\n",
    "        INT, '(', INT, ')'\n",
    "    )\n",
    ").interpretation(\n",
    "    Number.value\n",
    ")\n",
    "\n",
    "NUMBER = rule(\n",
    "    PREFIX,\n",
    "    VALUE\n",
    ").interpretation(\n",
    "    Number\n",
    ")\n",
    "\n",
    "test_match(\n",
    "    NUMBER,\n",
    "    '№ 154',\n",
    "    '№ 1-3',\n",
    "    '№ 1 - 3',\n",
    "    'номер 3(2)'\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "Pages = fact(\n",
    "    'Page',\n",
    "    ['value']\n",
    ")\n",
    "\n",
    "\n",
    "VALUE = INT.interpretation(\n",
    "    Pages.value\n",
    ")\n",
    "\n",
    "SUFFIX = rule(\n",
    "    caseless('с'),\n",
    "    DOT\n",
    ")\n",
    "\n",
    "PAGES = rule(\n",
    "    VALUE,\n",
    "    SUFFIX\n",
    ").interpretation(\n",
    "    Pages\n",
    ")\n",
    "\n",
    "test_match(\n",
    "    PAGES,\n",
    "    '416 с.'\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Size"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "Size = fact(\n",
    "    'Size',\n",
    "    ['value', 'measure']\n",
    ")\n",
    "\n",
    "\n",
    "def normalize_float(value):\n",
    "    return float(value.replace(',', '.'))\n",
    "\n",
    "\n",
    "FLOAT = rule(\n",
    "    INT,\n",
    "    in_({',', '.'}),\n",
    "    INT\n",
    ").interpretation(\n",
    "    interp.custom(normalize_float)\n",
    ")\n",
    "\n",
    "\n",
    "def remove_spaces(value):\n",
    "    return re.sub('\\s+', '', value)\n",
    "\n",
    "\n",
    "def normalize_separated_int(value):\n",
    "    return int(remove_spaces(value))\n",
    "\n",
    "\n",
    "SEPARATED_INT = or_(\n",
    "    rule(INT),\n",
    "    rule(INT, INT),\n",
    "    rule(INT, INT, INT)\n",
    ").interpretation(\n",
    "    interp.custom(normalize_separated_int)\n",
    ")\n",
    "\n",
    "VALUE = or_(\n",
    "    FLOAT,\n",
    "    SEPARATED_INT\n",
    ").interpretation(\n",
    "    Size.value\n",
    ")\n",
    "\n",
    "MEASURES = {\n",
    "    'мб': 'MB',\n",
    "    'кб': 'KB',\n",
    "    'байт': 'B'\n",
    "}\n",
    "\n",
    "MEASURE = dictionary(MEASURES).interpretation(\n",
    "    Size.measure.normalized().custom(MEASURES.get)\n",
    ")\n",
    "\n",
    "SIZE = rule(\n",
    "    VALUE,\n",
    "    MEASURE\n",
    ").interpretation(\n",
    "    Size\n",
    ")\n",
    "\n",
    "\n",
    "test_interpretation(\n",
    "    SIZE,\n",
    "    ['364 МБ', Size(value=364, measure='MB')],\n",
    "    ['6,12 Мб', Size(value=6.12, measure='MB')],\n",
    "    ['14 000 000 кб', Size(value=14000000, measure='KB')],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Duration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "Duration = fact(\n",
    "    'Duration',\n",
    "    ['hours', 'minutes', 'seconds']\n",
    ")\n",
    "\n",
    "\n",
    "HOURS = INT.interpretation(\n",
    "    Duration.hours.custom(int)\n",
    ")\n",
    "\n",
    "HOUR_WORDS = normalized('час')\n",
    "\n",
    "MINUTES = and_(\n",
    "    INT,\n",
    "    gte(0),\n",
    "    lte(59)\n",
    ").interpretation(\n",
    "    Duration.minutes.custom(int)\n",
    ")\n",
    "\n",
    "MINUTE_WORDS = or_(\n",
    "    rule(normalized('минута')),\n",
    "    rule(caseless('мин'), DOT.optional())\n",
    ")\n",
    "\n",
    "SECONDS = and_(\n",
    "    INT,\n",
    "    gte(0),\n",
    "    lte(59)\n",
    ").interpretation(\n",
    "    Duration.seconds.custom(int)\n",
    ")\n",
    "\n",
    "SECOND_WORDS = or_(\n",
    "    rule(normalized('секунда')),\n",
    "    rule(caseless('сек'), DOT.optional()),\n",
    ")\n",
    "\n",
    "DURATION = rule(\n",
    "    rule(\n",
    "        HOURS,\n",
    "        HOUR_WORDS\n",
    "    ).optional(),\n",
    "    rule(\n",
    "        MINUTES,\n",
    "        MINUTE_WORDS\n",
    "    ).optional(),\n",
    "    rule(\n",
    "        SECONDS,\n",
    "        SECOND_WORDS\n",
    "    )\n",
    ").interpretation(\n",
    "    Duration\n",
    ")\n",
    "\n",
    "\n",
    "test_interpretation(\n",
    "    DURATION,\n",
    "    ['50 секунд', Duration(seconds=50)],\n",
    "    ['02 час 11 мин. 50 сек.', Duration(hours=2, minutes=11, seconds=50)],\n",
    "    ['1 час  3  минуты  24 секунды', Duration(hours=1, minutes=3, seconds=24)],\n",
    "    ['3 минуты 23 секунды', Duration(minutes=3, seconds=23)],\n",
    "    ['05 мин. 57 сек.', Duration(minutes=5, seconds=57)]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Source"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "Source = fact(\n",
    "    'Source',\n",
    "    ['type', 'value']\n",
    ")\n",
    "\n",
    "\n",
    "TYPE = morph_pipeline([\n",
    "    'cd-r диск',\n",
    "    'dvd диск',\n",
    "    'двд-диск',\n",
    "    'компакт-диске',\n",
    "    'папка',\n",
    "    'журнал',\n",
    "    'сборник',\n",
    "\n",
    "    'сеть',\n",
    "    'ресурс',\n",
    "    'сайт',\n",
    "]).interpretation(\n",
    "    Source.type.normalized()\n",
    ")\n",
    "\n",
    "VALUE = or_(\n",
    "    NUMBER,\n",
    "    TITLE\n",
    ").interpretation(\n",
    "    Source.value\n",
    ")\n",
    "\n",
    "SOURCE = rule(\n",
    "    TYPE,\n",
    "    VALUE,\n",
    ").interpretation(\n",
    "    Source\n",
    ")\n",
    "\n",
    "\n",
    "test_interpretation(\n",
    "    SOURCE,\n",
    "    ['DVD диске № 23', Source(type='dvd диск', value=Number(value='23'))],\n",
    "    ['папка «Указы»', Source(type='папка', value='«Указы»')],\n",
    "    ['сети «Интернет»', Source(type='сеть', value='«Интернет»')]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Date"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "Date = fact(\n",
    "    'Date',\n",
    "    ['year', 'month', 'day']\n",
    ")\n",
    "\n",
    "\n",
    "MONTHS = {\n",
    "    'январь': 1,\n",
    "    'февраль': 2,\n",
    "    'март': 3,\n",
    "    'апрель': 4,\n",
    "    'май': 5,\n",
    "    'июнь': 6,\n",
    "    'июль': 7,\n",
    "    'август': 8,\n",
    "    'сентябрь': 9,\n",
    "    'октябрь': 10,\n",
    "    'ноябрь': 11,\n",
    "    'декабрь': 12,\n",
    "}\n",
    "\n",
    "MONTH_NAME = dictionary(MONTHS).interpretation(\n",
    "    Date.month.normalized().custom(MONTHS.get)\n",
    ")\n",
    "\n",
    "MONTH = and_(\n",
    "    INT,\n",
    "    gte(1),\n",
    "    lte(12)\n",
    ").interpretation(\n",
    "    Date.month.custom(int)\n",
    ")\n",
    "\n",
    "YEAR = and_(\n",
    "    INT,\n",
    "    gte(1000),\n",
    "    lte(3000)\n",
    ").interpretation(\n",
    "    Date.year.custom(int)\n",
    ")\n",
    "\n",
    "YEAR_SUFFIX = rule(\n",
    "    or_(\n",
    "        eq('г'),\n",
    "        normalized('год')\n",
    "    ),\n",
    "    DOT.optional()\n",
    ")\n",
    "\n",
    "DAY = and_(\n",
    "    INT,\n",
    "    gte(1),\n",
    "    lte(31)\n",
    ").interpretation(\n",
    "    Date.day.custom(int)\n",
    ")\n",
    "\n",
    "DATE = or_(\n",
    "    rule(\n",
    "        YEAR,\n",
    "        YEAR_SUFFIX\n",
    "    ),\n",
    "    rule(\n",
    "        MONTH_NAME,\n",
    "        YEAR\n",
    "    ),\n",
    "    rule(\n",
    "        DAY,\n",
    "        DOT,\n",
    "        MONTH,\n",
    "        DOT,\n",
    "        YEAR\n",
    "    ),\n",
    "    rule(\n",
    "        DAY,\n",
    "        MONTH_NAME,\n",
    "        YEAR\n",
    "    ),\n",
    "\n",
    ")\n",
    "\n",
    "SUFFIX = normalized('издание')\n",
    "\n",
    "DATE = rule(\n",
    "    DATE,\n",
    "    SUFFIX.optional()\n",
    ").interpretation(\n",
    "    Date\n",
    ")\n",
    "\n",
    "test_interpretation(\n",
    "    DATE,\n",
    "    ['2007г. издания', Date(year=2007)],\n",
    "    ['июнь 2005', Date(year=2005, month=6)],\n",
    "    ['12.01.2004', Date(year=2004, month=1, day=12)],\n",
    "    ['31 декабря 1990', Date(year=1990, month=12, day=31)]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sud"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "Sud = fact(\n",
    "    'Sud',\n",
    "    ['name', 'type']\n",
    ")\n",
    "\n",
    "\n",
    "CAPITALIZED = is_capitalized()\n",
    "\n",
    "GEO = or_(\n",
    "    rule(CAPITALIZED),\n",
    "    rule(\n",
    "        CAPITALIZED,\n",
    "        DASH.optional(),\n",
    "        CAPITALIZED\n",
    "    )\n",
    ")\n",
    "\n",
    "NUMERAL = rule(INT)\n",
    "\n",
    "NAME = or_(\n",
    "    GEO,\n",
    "    NUMERAL,\n",
    ").interpretation(\n",
    "    Sud.name.normalized()\n",
    ")\n",
    "\n",
    "TYPE = dictionary({\n",
    "    'федеральный',\n",
    "    'областной',\n",
    "    'городской',\n",
    "    'районный',\n",
    "    'гарнизонный',\n",
    "    'военный'\n",
    "})\n",
    "\n",
    "TYPE = or_(\n",
    "    rule(TYPE),\n",
    "    rule(TYPE, TYPE)\n",
    ").interpretation(\n",
    "    Sud.type.normalized()\n",
    ")\n",
    "\n",
    "SUD = rule(\n",
    "    NAME.optional(),\n",
    "    TYPE.optional(),\n",
    "    normalized('суд')\n",
    ").interpretation(\n",
    "    Sud\n",
    ")\n",
    "\n",
    "\n",
    "test_interpretation(\n",
    "    SUD,\n",
    "    ['Тверского районного суда', Sud(name='тверской', type='районный')],\n",
    "    ['Бугурусланского городского суда', Sud(name='бугурусланский', type='городской')],\n",
    "    ['Вытегорского федерального районного суда', Sud(name='вытегорский', type='федеральный районный')],\n",
    "    ['109 гарнизонного военного суда', Sud(name='109', type='гарнизонный военный')],\n",
    "    ['Тверского суда', Sud(name='тверской')],\n",
    "    ['Федерального суда', Sud(type='федеральный')]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Decision"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "Decision = fact(\n",
    "    'Decision',\n",
    "    ['sud', 'date']\n",
    ")\n",
    "\n",
    "\n",
    "DECISION = rule(\n",
    "    SUD.interpretation(\n",
    "        Decision.sud\n",
    "    ),\n",
    "    DATE.interpretation(\n",
    "        Decision.date\n",
    "    )\n",
    ").interpretation(\n",
    "    Decision\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Fsem"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "Item = fact(\n",
    "    'Item',\n",
    "    ['type',\n",
    "     attribute('titles').repeatable(),\n",
    "     attribute('urls').repeatable(),\n",
    "     'author', 'number', 'source',\n",
    "     'pages', 'duration', 'size',\n",
    "     'date'\n",
    "    ]\n",
    ")\n",
    "Fsem = fact(\n",
    "    'Fsem',\n",
    "    [attribute('items').repeatable(), 'decision']\n",
    ")\n",
    "\n",
    "\n",
    "ATTRIBUTE = or_(\n",
    "    TITLE.interpretation(Item.titles),\n",
    "    URL.interpretation(Item.urls),\n",
    "    AUTHOR.interpretation(Item.author),\n",
    "    NUMBER.interpretation(Item.number),\n",
    "    PAGES.interpretation(Item.pages),\n",
    "    SIZE.interpretation(Item.size),\n",
    "    DURATION.interpretation(Item.duration),\n",
    "    SOURCE.interpretation(Item.source),\n",
    "    DATE.interpretation(Item.date)\n",
    ")\n",
    "\n",
    "ITEM = rule(\n",
    "    MAIN_TYPE.interpretation(Item.type),\n",
    "    ATTRIBUTE.repeatable()\n",
    ").interpretation(\n",
    "    Item\n",
    ")\n",
    "\n",
    "FSEM = rule(\n",
    "    ITEM.interpretation(\n",
    "        Fsem.items\n",
    "    ).repeatable(),\n",
    "    DECISION.interpretation(\n",
    "        Fsem.decision\n",
    "    )\n",
    ").interpretation(\n",
    "    Fsem\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Extractor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Книга «Справочник русского человека» автор Иванов Алексей Аркадьевич, \n",
      "───── ────────────────────────────── ───────────────────────────────  \n",
      "издательство «ФЕРИ-В», отпечатанная в ООО «Типография ИПО Профиздат», \n",
      "─────────────────────                     ──────────────────────────  \n",
      "2004 г, г. Москва., Крутицкий вал., д. 18 (решение Лефортовского \n",
      "──────                                             ──────────────\n",
      "районного суда г. Москвы от 15.10.2012);\n",
      "──────────────              ──────────  \n",
      "{\n",
      "  \"items\": [\n",
      "    {\n",
      "      \"type\": \"книга\",\n",
      "      \"titles\": [\n",
      "        \"«Справочник русского человека»\",\n",
      "        \"«Типография ИПО Профиздат»\"\n",
      "      ],\n",
      "      \"urls\": [],\n",
      "      \"author\": {\n",
      "        \"type\": \"издательство\",\n",
      "        \"name\": \"«ФЕРИ-В»\"\n",
      "      },\n",
      "      \"date\": {\n",
      "        \"year\": 2004\n",
      "      }\n",
      "    }\n",
      "  ],\n",
      "  \"decision\": {\n",
      "    \"sud\": {\n",
      "      \"name\": \"лефортовский\",\n",
      "      \"type\": \"районный\"\n",
      "    },\n",
      "    \"date\": {\n",
      "      \"year\": 2012,\n",
      "      \"month\": 10,\n",
      "      \"day\": 15\n",
      "    }\n",
      "  }\n",
      "}\n",
      "Видеозапись «Проект увечье - Маршрутка» длительностью 3 минуты 23 \n",
      "─────────── ───────────────────────────               ────────────\n",
      "секунды, размещенная на интернет-странице «Денис Павлович» по сетевому\n",
      "───────                                   ────────────────            \n",
      " адресу http://vk.com/videol924006_165972484 (решение Вахитовского \n",
      "        ────────────────────────────────────          ─────────────\n",
      "районного суда г. Казани от 08.04.2016);\n",
      "──────────────              ──────────  \n",
      "{\n",
      "  \"items\": [\n",
      "    {\n",
      "      \"type\": \"видеозапись\",\n",
      "      \"titles\": [\n",
      "        \"«Проект увечье - Маршрутка»\",\n",
      "        \"«Денис Павлович»\"\n",
      "      ],\n",
      "      \"urls\": [\n",
      "        \"http://vk.com/videol924006_165972484\"\n",
      "      ],\n",
      "      \"duration\": {\n",
      "        \"minutes\": 3,\n",
      "        \"seconds\": 23\n",
      "      }\n",
      "    }\n",
      "  ],\n",
      "  \"decision\": {\n",
      "    \"sud\": {\n",
      "      \"name\": \"вахитовский\",\n",
      "      \"type\": \"районный\"\n",
      "    },\n",
      "    \"date\": {\n",
      "      \"year\": 2016,\n",
      "      \"month\": 4,\n",
      "      \"day\": 8\n",
      "    }\n",
      "  }\n",
      "}\n",
      "Статья Бориса Стомахина «Время раздирать», размещенная на интернет-\n",
      "──────                  ─────────────────                          \n",
      "сайте http://sopritivlenie.marsho.net (решение Останкинского районного\n",
      "      ───────────────────────────────          ───────────────────────\n",
      " суда г. Москвы от 22.11.2013);\n",
      "─────              ──────────  \n",
      "{\n",
      "  \"items\": [\n",
      "    {\n",
      "      \"type\": \"статья\",\n",
      "      \"titles\": [\n",
      "        \"«Время раздирать»\"\n",
      "      ],\n",
      "      \"urls\": [\n",
      "        \"http://sopritivlenie.marsho.net\"\n",
      "      ]\n",
      "    }\n",
      "  ],\n",
      "  \"decision\": {\n",
      "    \"sud\": {\n",
      "      \"name\": \"останкинский\",\n",
      "      \"type\": \"районный\"\n",
      "    },\n",
      "    \"date\": {\n",
      "      \"year\": 2013,\n",
      "      \"month\": 11,\n",
      "      \"day\": 22\n",
      "    }\n",
      "  }\n",
      "}\n",
      "Музыкальное произведение - аудиозапись «Караоке 282 - Панцерваген \n",
      "────────────────────────   ─────────── ───────────────────────────\n",
      "расписной» продолжительностью около 3 минут 41 секунды (решение \n",
      "──────────                          ──────────────────          \n",
      "Кировского районного суда г. Ростова-на-Дону от 02.03.2016);\n",
      "─────────────────────────                       ──────────  \n",
      "{\n",
      "  \"items\": [\n",
      "    {\n",
      "      \"type\": \"аудиозапись\",\n",
      "      \"titles\": [\n",
      "        \"«Караоке 282 - Панцерваген расписной»\"\n",
      "      ],\n",
      "      \"urls\": [],\n",
      "      \"duration\": {\n",
      "        \"minutes\": 3,\n",
      "        \"seconds\": 41\n",
      "      }\n",
      "    }\n",
      "  ],\n",
      "  \"decision\": {\n",
      "    \"sud\": {\n",
      "      \"name\": \"кировский\",\n",
      "      \"type\": \"районный\"\n",
      "    },\n",
      "    \"date\": {\n",
      "      \"year\": 2016,\n",
      "      \"month\": 3,\n",
      "      \"day\": 2\n",
      "    }\n",
      "  }\n",
      "}\n",
      "Указатель страницы сайта в сети «Интернет» с URL-адресом \n",
      "                           ───────────────               \n",
      "https://dowlah.wordpress.com (решение Новоуренгойского городского суда\n",
      "────────────────────────────          ────────────────────────────────\n",
      " Ямало-Ненецкого автономного округа от 30.11.2015);\n",
      "                                       ──────────  \n",
      "Видеоролик: «Благородная битва в Москве - Справедливое возмездие» \n",
      "──────────  ───────────────────────────────────────────────────── \n",
      "(решение Ленинского районного суда г. Томска от 12.04.2013);\n",
      "         ─────────────────────────              ──────────  \n",
      "{\n",
      "  \"items\": [\n",
      "    {\n",
      "      \"type\": \"видеоролик\",\n",
      "      \"titles\": [\n",
      "        \"«Благородная битва в Москве - Справедливое возмездие»\"\n",
      "      ],\n",
      "      \"urls\": []\n",
      "    }\n",
      "  ],\n",
      "  \"decision\": {\n",
      "    \"sud\": {\n",
      "      \"name\": \"ленинский\",\n",
      "      \"type\": \"районный\"\n",
      "    },\n",
      "    \"date\": {\n",
      "      \"year\": 2013,\n",
      "      \"month\": 4,\n",
      "      \"day\": 12\n",
      "    }\n",
      "  }\n",
      "}\n",
      "Стихотворение Александра Сталина «Обращение к ВВП» из сборника \n",
      "─────────────                    ─────────────────    ─────────\n",
      "«Запрещенные стихи» (решение Новоспасского районного суда Ульяновской \n",
      "───────────────────          ────────────────────────────             \n",
      "области от 02.02.2012);\n",
      "           ──────────  \n",
      "{\n",
      "  \"items\": [\n",
      "    {\n",
      "      \"type\": \"стихотворение\",\n",
      "      \"titles\": [\n",
      "        \"«Обращение к ВВП»\"\n",
      "      ],\n",
      "      \"urls\": [],\n",
      "      \"source\": {\n",
      "        \"type\": \"сборник\",\n",
      "        \"value\": \"«Запрещенные стихи»\"\n",
      "      }\n",
      "    }\n",
      "  ],\n",
      "  \"decision\": {\n",
      "    \"sud\": {\n",
      "      \"name\": \"новоспасский\",\n",
      "      \"type\": \"районный\"\n",
      "    },\n",
      "    \"date\": {\n",
      "      \"year\": 2012,\n",
      "      \"month\": 2,\n",
      "      \"day\": 2\n",
      "    }\n",
      "  }\n",
      "}\n",
      "Видеофайлы (видеоролики): под названием «Обращение Абдуль-Джаббара КБК\n",
      "──────────  ───────────                 ──────────────────────────────\n",
      " РЖ» длительностью 8 минут 02 секунды; под названием «Обращение Амира \n",
      "────               ──────────────────                ─────────────────\n",
      "Закарии КБК ИК» длительностью 8 минут 14 секунд, которые \n",
      "───────────────               ─────────────────          \n",
      "распространяются путем размещения на различных открытых для свободного\n",
      " доступа адресах сети Интернет, а также опубликованы для свободного \n",
      "доступа на следующих интернет-страницах: - \n",
      "«www.youtube.com/watch?v=PI3-e8FL8rg»; - \n",
      "─────────────────────────────────────    \n",
      "«www.youtube.com/watch?v=EgCazLnhCew» (решение Ленинского районного \n",
      "─────────────────────────────────────          ─────────────────────\n",
      "суда г. Костромы от 19.08.2013);\n",
      "────                ──────────  \n",
      "{\n",
      "  \"items\": [\n",
      "    {\n",
      "      \"type\": \"видеоролик\",\n",
      "      \"titles\": [\n",
      "        \"«Обращение Абдуль-Джаббара КБК РЖ»\",\n",
      "        \"«Обращение Амира Закарии КБК ИК»\",\n",
      "        \"«www.youtube.com/watch?v=PI3-e8FL8rg»\",\n",
      "        \"«www.youtube.com/watch?v=EgCazLnhCew»\"\n",
      "      ],\n",
      "      \"urls\": [],\n",
      "      \"duration\": {\n",
      "        \"minutes\": 8,\n",
      "        \"seconds\": 14\n",
      "      }\n",
      "    }\n",
      "  ],\n",
      "  \"decision\": {\n",
      "    \"sud\": {\n",
      "      \"name\": \"ленинский\",\n",
      "      \"type\": \"районный\"\n",
      "    },\n",
      "    \"date\": {\n",
      "      \"year\": 2013,\n",
      "      \"month\": 8,\n",
      "      \"day\": 19\n",
      "    }\n",
      "  }\n",
      "}\n",
      "Видеофайл под названием «Смотрите какие жиды есть на самом деле! \n",
      "─────────               ─────────────────────────────────────────\n",
      "Цитаты из «талмуда» священного п...» размещенный в информационно-\n",
      "───────────────────                                              \n",
      "телекоммуникационной сети «Интернет» на электронных адресах: - \n",
      "                     ───────────────                           \n",
      "http://kivvi.kz/watch/jvd2nd2vtkdi, - \n",
      "──────────────────────────────────    \n",
      "http://my.mail.ru/mail/pushkar-05/video/_myvideo/53.html, - \n",
      "────────────────────────────────────────────────────────    \n",
      "https://vk.com/videos153457036?z=video153457036_166480187, - \n",
      "─────────────────────────────────────────────────────────    \n",
      "https://vk.com/video-67405870_169503660 (решение Яльчикского районного\n",
      "───────────────────────────────────────          ─────────────────────\n",
      " суда Чувашской Республики - Чувашии от 25.03.2016);\n",
      "─────                                   ──────────  \n",
      "{\n",
      "  \"items\": [\n",
      "    {\n",
      "      \"type\": \"видеофайл\",\n",
      "      \"titles\": [\n",
      "        \"«Смотрите какие жиды есть на самом деле! Цитаты из «талмуда»\"\n",
      "      ],\n",
      "      \"urls\": [\n",
      "        \"http://kivvi.kz/watch/jvd2nd2vtkdi\",\n",
      "        \"http://my.mail.ru/mail/pushkar-05/video/_myvideo/53.html\",\n",
      "        \"https://vk.com/videos153457036?z=video153457036_166480187\",\n",
      "        \"https://vk.com/video-67405870_169503660\"\n",
      "      ],\n",
      "      \"source\": {\n",
      "        \"type\": \"сеть\",\n",
      "        \"value\": \"«Интернет»\"\n",
      "      }\n",
      "    }\n",
      "  ],\n",
      "  \"decision\": {\n",
      "    \"sud\": {\n",
      "      \"name\": \"яльчикский\",\n",
      "      \"type\": \"районный\"\n",
      "    },\n",
      "    \"date\": {\n",
      "      \"year\": 2016,\n",
      "      \"month\": 3,\n",
      "      \"day\": 25\n",
      "    }\n",
      "  }\n",
      "}\n",
      "Видеофильм «Приморские герои! Слава им! mp4», размещенный в сети \n",
      "────────── ─────────────────────────────────                     \n",
      "Интернет по адресу: http://vk.com/video26939759 160848619 (решение \n",
      "                    ───────────────────────────                    \n",
      "Бабушкинского районного суда г. Москвы от 05.06.2014);\n",
      "────────────────────────────              ──────────  \n",
      "{\n",
      "  \"items\": [\n",
      "    {\n",
      "      \"type\": \"видеофильм\",\n",
      "      \"titles\": [\n",
      "        \"«Приморские герои! Слава им! mp4»\"\n",
      "      ],\n",
      "      \"urls\": [\n",
      "        \"http://vk.com/video26939759\"\n",
      "      ]\n",
      "    }\n",
      "  ],\n",
      "  \"decision\": {\n",
      "    \"sud\": {\n",
      "      \"name\": \"бабушкинский\",\n",
      "      \"type\": \"районный\"\n",
      "    },\n",
      "    \"date\": {\n",
      "      \"year\": 2014,\n",
      "      \"month\": 6,\n",
      "      \"day\": 5\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "def join_spans(text, spans):\n",
    "    spans = sorted(spans)\n",
    "    return ' '.join(\n",
    "        text[start:stop]\n",
    "        for start, stop in spans\n",
    "    )\n",
    "\n",
    "\n",
    "class Match(object):\n",
    "    def __init__(self, fact, spans):\n",
    "        self.fact = fact\n",
    "        self.spans = spans\n",
    "\n",
    "\n",
    "DEBUG = or_(\n",
    "    MAIN_TYPE,\n",
    "    TITLE,\n",
    "    URL,\n",
    "    AUTHOR,\n",
    "    NUMBER,\n",
    "    PAGES,\n",
    "    SIZE,\n",
    "    DURATION,\n",
    "    SOURCE,\n",
    "    DATE,\n",
    "    SUD\n",
    ")\n",
    "\n",
    "\n",
    "TOKENIZER = MorphTokenizer().add_rules(URL_RULE)\n",
    "\n",
    "\n",
    "class Extractor(object):\n",
    "    def __init__(self):\n",
    "        self.debug = Parser(DEBUG, tokenizer=TOKENIZER)\n",
    "        self.parser = Parser(FSEM, tokenizer=TOKENIZER)\n",
    "\n",
    "    def __call__(self, text):\n",
    "        matches = self.debug.findall(text)\n",
    "        spans = [_.span for _ in matches]\n",
    "\n",
    "        line = join_spans(text, spans)\n",
    "        matches = list(self.parser.findall(line))\n",
    "        fact = None\n",
    "        if matches:\n",
    "            match = matches[0]\n",
    "            fact = match.fact\n",
    "\n",
    "        return Match(fact, spans)\n",
    "\n",
    "    \n",
    "extractor = Extractor()\n",
    "\n",
    "# 15 ошибок, 10 полуошибок из 100\n",
    "seed(11)\n",
    "for text in sample(texts, 10):\n",
    "    match = extractor(text)\n",
    "    show_markup(text, match.spans)\n",
    "    if match.fact:\n",
    "        show_json(match.fact.as_json)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
